// lib/src/services/storage_service.dart

import 'dart:convert';
import 'dart:io';
import 'package:csv/csv.dart';

import '../models/customer.dart';
import '../models/shipment.dart';
import 'shipment_service.dart'; // 引入用于状态常量

/// 数据存储服务，使用JSON文件作为持久化存储。
/// 负责管理客户和运单数据。
class StorageService {
  static const String _customersFileName = 'data/customers.json';
  static const String _shipmentsFileName = 'data/shipments.json';

  List<Customer> _customers = [];
  List<Shipment> _shipments = [];
  bool _isInitialized = false;

  StorageService._();

  static Future<StorageService> create() async {
    final service = StorageService._();
    await service.init();
    return service;
  }

  Future<void> init() async {
    if (_isInitialized) return;
    _ensureDataDirectoryExists();
    await Future.wait([_loadCustomersFromFile(), _loadShipmentsFromFile()]);
    _isInitialized = true;
    print("StorageService initialized, data loaded.");
  }

  void _ensureDataDirectoryExists() {
    final directory = Directory('data');
    if (!directory.existsSync()) {
      directory.createSync(recursive: true);
      print("Data directory 'data' created.");
    }
  }

  Future<void> _loadCustomersFromFile() async {
    try {
      final file = File(_customersFileName);
      if (await file.exists()) {
        final content = await file.readAsString(encoding: utf8);
        if (content.isNotEmpty) {
          final List<dynamic> jsonData = jsonDecode(content);
          _customers =
              jsonData
                  .map((item) => Customer.fromMap(item as Map<String, dynamic>))
                  .toList();
          print(
            'Customer data loaded successfully from $_customersFileName (UTF-8).',
          );
        } else {
          _customers = [];
          print('Customer data file $_customersFileName is empty.');
        }
      } else {
        _customers = [];
        print(
          'Customer data file $_customersFileName not found. A new file will be created upon saving.',
        );
        await _saveCustomersToFile();
      }
    } catch (e) {
      print('Error loading customer data from file: $e');
      _customers = [];
    }
  }

  Future<void> _saveCustomersToFile() async {
    try {
      final file = File(_customersFileName);
      final List<Map<String, dynamic>> jsonData =
          _customers.map((customer) => customer.toMap()).toList();
      await file.writeAsString(
        jsonEncode(jsonData),
        encoding: utf8,
        flush: true,
      );
    } catch (e) {
      print('Error saving customer data to file: $e');
    }
  }

  Future<bool> addCustomer(Customer customer) async {
    if (_customers.any((c) => c.customerCode == customer.customerCode)) {
      return false;
    }
    _customers.add(customer);
    await _saveCustomersToFile();
    return true;
  }

  Customer? getCustomerByCode(String customerCode) {
    try {
      return _customers.firstWhere((c) => c.customerCode == customerCode);
    } catch (e) {
      return null;
    }
  }

  List<Customer> getAllCustomers() {
    return List.from(_customers);
  }

  // Helper to format row data for error messages
  String _formatRowForError(List<dynamic> row) {
    return row
        .map((val) => '"${val.toString().replaceAll('"', '""')}"')
        .join(', ');
  }

  Future<Map<String, dynamic>> importCustomersFromCsv(String filePath) async {
    int successCount = 0;
    int failureCount = 0;
    List<String> errorDetails = [];

    try {
      final file = File(filePath);
      if (!await file.exists()) {
        errorDetails.add('File not found: $filePath');
        return {
          'success': successCount,
          'failures': failureCount,
          'errors': errorDetails,
        };
      }

      final csvString = await file.readAsString(encoding: utf8);
      // Handle potential BOM (Byte Order Mark) at the start of UTF-8 files
      final cleanCsvString =
          csvString.startsWith('\uFEFF') ? csvString.substring(1) : csvString;

      final List<List<dynamic>> rowsAsListOfValues = const CsvToListConverter(
        eol: '\n', // Be careful if CSV uses \r\n
        fieldDelimiter: ',',
        shouldParseNumbers: false, // Treat all as strings initially
      ).convert(cleanCsvString);

      if (rowsAsListOfValues.isEmpty) {
        errorDetails.add('CSV file is empty or could not be parsed.');
        return {
          'success': successCount,
          'failures': failureCount,
          'errors': errorDetails,
        };
      }

      final headers =
          rowsAsListOfValues[0].map((e) => e.toString().trim()).toList();
      final expectedHeaders = [
        'customerCode',
        'customerName',
        'contactInfo',
        'countryCode',
        'languageCode',
        'timezoneOffsetHours',
      ];

      bool headersMatch =
          expectedHeaders.length <=
              headers.length && // Allow extra columns in CSV
          expectedHeaders.every((h) => headers.contains(h));
      if (!headersMatch) {
        errorDetails.add(
          'CSV headers do not match expected format. Expected columns (at least): ${expectedHeaders.join(',')}. Found: ${headers.join(',')}',
        );
        return {
          'success': successCount,
          'failures': failureCount,
          'errors': errorDetails,
        };
      }

      for (int i = 1; i < rowsAsListOfValues.length; i++) {
        final row = rowsAsListOfValues[i];
        // Defensive check in case of short lines
        if (row.length < expectedHeaders.length) {
          failureCount++;
          errorDetails.add(
            'Row ${i + 1}: Incorrect number of columns. Expected at least ${expectedHeaders.length}, got ${row.length}. Data: [${_formatRowForError(row)}]',
          );
          continue;
        }

        String customerCode = "";
        String customerName = "";
        String contactInfo = "";
        String countryCode = "";
        String languageCode = "";
        String timezoneOffsetHoursString = "";
        int? timezoneOffsetHours;

        try {
          // Use .trim() on string values read from CSV
          customerCode = row[headers.indexOf('customerCode')].toString().trim();
          customerName = row[headers.indexOf('customerName')].toString().trim();
          contactInfo = row[headers.indexOf('contactInfo')].toString().trim();
          countryCode = row[headers.indexOf('countryCode')].toString().trim();
          languageCode = row[headers.indexOf('languageCode')].toString().trim();
          timezoneOffsetHoursString =
              row[headers.indexOf('timezoneOffsetHours')].toString().trim();

          if (customerCode.isEmpty ||
              customerName.isEmpty ||
              languageCode.isEmpty ||
              timezoneOffsetHoursString.isEmpty) {
            failureCount++;
            errorDetails.add(
              'Row ${i + 1}: Missing required fields (customerCode, customerName, languageCode, timezoneOffsetHours). Data: [${_formatRowForError(row)}]',
            );
            continue;
          }

          timezoneOffsetHours = int.tryParse(timezoneOffsetHoursString);

          if (timezoneOffsetHours == null) {
            failureCount++;
            errorDetails.add(
              'Row ${i + 1}: Invalid timezoneOffsetHours (must be an integer). Value: "$timezoneOffsetHoursString". Data: [${_formatRowForError(row)}]',
            );
            continue;
          }

          final customer = Customer(
            customerCode: customerCode,
            customerName: customerName,
            contactInfo: contactInfo,
            countryCode: countryCode,
            languageCode: languageCode,
            timezoneOffsetHours: timezoneOffsetHours,
          );

          if (await addCustomer(customer)) {
            successCount++;
          } else {
            failureCount++;
            errorDetails.add(
              'Row ${i + 1}: Failed to add customer (likely duplicate customerCode: "$customerCode"). Data: [${_formatRowForError(row)}]',
            );
          }
        } catch (e) {
          failureCount++;
          errorDetails.add(
            'Row ${i + 1}: Error processing row. ${e.toString()}. Data: [${_formatRowForError(row)}]',
          );
        }
      }
    } catch (e) {
      failureCount++;
      errorDetails.add('Error during CSV import process: ${e.toString()}');
    }

    return {
      'success': successCount,
      'failures': failureCount,
      'errors': errorDetails,
    };
  }

  // --- 运单相关方法 (保持不变) ---
  Future<void> _loadShipmentsFromFile() async {
    try {
      final file = File(_shipmentsFileName);
      if (await file.exists()) {
        final content = await file.readAsString(encoding: utf8);
        if (content.isNotEmpty) {
          final List<dynamic> jsonData = jsonDecode(content);
          _shipments =
              jsonData
                  .map((item) => Shipment.fromMap(item as Map<String, dynamic>))
                  .toList();
          print(
            'Shipment data loaded successfully from $_shipmentsFileName (UTF-8).',
          );
        } else {
          _shipments = [];
          print('Shipment data file $_shipmentsFileName is empty.');
        }
      } else {
        _shipments = [];
        print(
          'Shipment data file $_shipmentsFileName not found. A new file will be created upon saving.',
        );
        await _saveShipmentsToFile();
      }
    } catch (e) {
      print('Error loading shipment data from file: $e');
      _shipments = [];
    }
  }

  Future<void> _saveShipmentsToFile() async {
    try {
      final file = File(_shipmentsFileName);
      final List<Map<String, dynamic>> jsonData =
          _shipments.map((shipment) => shipment.toMap()).toList();
      await file.writeAsString(
        jsonEncode(jsonData),
        encoding: utf8,
        flush: true,
      );
    } catch (e) {
      print('Error saving shipment data to file: $e');
    }
  }

  Future<void> addShipment(Shipment shipment) async {
    if (getCustomerByCode(shipment.customerCode) == null) {
      print(
        'Warning: Customer code ${shipment.customerCode} for shipment ${shipment.trackingNumber} does not exist. Shipment will still be added, but ensure customer data is correct.',
      );
    }
    _shipments.add(shipment);
    await _saveShipmentsToFile();
  }

  Shipment? getShipmentByTrackingNumber(String trackingNumber) {
    try {
      return _shipments.firstWhere((s) => s.trackingNumber == trackingNumber);
    } catch (e) {
      return null;
    }
  }

  List<Shipment> getShipmentsByCustomerCode(String customerCode) {
    return _shipments.where((s) => s.customerCode == customerCode).toList();
  }

  List<Shipment> getAllShipments() {
    return List.from(_shipments);
  }

  Future<bool> updateShipment(Shipment updatedShipment) async {
    final index = _shipments.indexWhere(
      (s) => s.trackingNumber == updatedShipment.trackingNumber,
    );
    if (index != -1) {
      _shipments[index] = updatedShipment;
      await _saveShipmentsToFile();
      return true;
    }
    return false;
  }

  Future<void> initializeWithSampleData({bool forceOverwrite = false}) async {
    if (!forceOverwrite) {
      bool customersFileExistsAndNotEmpty = false;
      final customersFile = File(_customersFileName);
      if (await customersFile.exists()) {
        final content = await customersFile.readAsString(encoding: utf8);
        if (content.isNotEmpty && content != '[]') {
          customersFileExistsAndNotEmpty = true;
        }
      }

      bool shipmentsFileExistsAndNotEmpty = false;
      final shipmentsFile = File(_shipmentsFileName);
      if (await shipmentsFile.exists()) {
        final content = await shipmentsFile.readAsString(encoding: utf8);
        if (content.isNotEmpty && content != '[]') {
          shipmentsFileExistsAndNotEmpty = true;
        }
      }

      if (customersFileExistsAndNotEmpty || shipmentsFileExistsAndNotEmpty) {
        print(
          "Existing data files detected and are not empty. Skipping sample data initialization. Use --force to overwrite.",
        );
        return;
      }
    }

    print("Initializing sample data and saving to files (UTF-8)...");
    _customers.clear();
    _shipments.clear();

    final customer1 = Customer(
      customerCode: 'NTS-2304',
      customerName: 'Ruixin Tech (Sample)',
      contactInfo: '+966551234567',
      countryCode: 'SA',
      languageCode: 'ar',
      timezoneOffsetHours: 3,
    );
    final customer2 = Customer(
      customerCode: 'GLOBAL-EX',
      customerName: 'Global Exports LLC (Sample)',
      contactInfo: '+12125550100',
      countryCode: 'US',
      languageCode: 'en',
      timezoneOffsetHours: -5,
    );

    _customers.add(customer1);
    _customers.add(customer2);
    await _saveCustomersToFile();

    final shipment1 = Shipment(
      customerCode: 'NTS-2304',
      cargoShortName: 'ElecComp',
      packageCount: 10,
      departurePortTimezoneOffsetHours: 8,
      transportMode: ShipmentService.transportAir,
      estimatedTransitDays: 5,
      status: ShipmentService.statusNotShipped,
      cargoDetails: 'Electronic Components, Model ABC123, 10 Boxes (Sample)',
    );

    final shipment2Status = ShipmentService.statusShipped;
    final shipment2ShippedDate = DateTime.now().toUtc().subtract(
      Duration(days: 2),
    );
    final shipment2 = Shipment(
      customerCode: 'NTS-2304',
      cargoShortName: 'Textiles',
      packageCount: 50,
      departurePortTimezoneOffsetHours: 8,
      transportMode: ShipmentService.transportSea,
      estimatedTransitDays: 25,
      status: shipment2Status,
      cargoDetails: 'Textile Fabrics, 50 Rolls (Sample)',
      shippedDateUtc: shipment2ShippedDate,
    );
    if (shipment2.status == ShipmentService.statusShipped &&
        shipment2.shippedDateUtc != null) {
      shipment2.estimatedArrivalDateUtc = shipment2.shippedDateUtc!.add(
        Duration(days: shipment2.estimatedTransitDays),
      );
    }

    final shipment3Status = ShipmentService.statusArrived;
    final shipment3ShippedDate = DateTime.now().toUtc().subtract(
      Duration(days: 4),
    );
    final shipment3ActualArrival = DateTime.now().toUtc().subtract(
      Duration(days: 1),
    );
    final shipment3 = Shipment(
      customerCode: 'GLOBAL-EX',
      cargoShortName: 'MachParts',
      packageCount: 2,
      departurePortTimezoneOffsetHours: 1,
      transportMode: ShipmentService.transportAir,
      estimatedTransitDays: 3,
      status: shipment3Status,
      cargoDetails: 'Precision Machine Parts, 2 Pallets (Sample)',
      shippedDateUtc: shipment3ShippedDate,
      actualArrivalDateUtc: shipment3ActualArrival,
    );
    if ((shipment3.status == ShipmentService.statusShipped ||
            shipment3.status == ShipmentService.statusArrived) &&
        shipment3.shippedDateUtc != null) {
      shipment3.estimatedArrivalDateUtc = shipment3.shippedDateUtc!.add(
        Duration(days: shipment3.estimatedTransitDays),
      );
    }

    _shipments.add(shipment1);
    _shipments.add(shipment2);
    _shipments.add(shipment3);
    await _saveShipmentsToFile();

    print("Sample data initialization and saving completed.");
  }
}
